﻿namespace ImapComponent.Net.Mail.Mime
{
    using System;
    using System.Net.Mail;
    using System.Text.RegularExpressions;

    using ImapComponent.Net.Mail.Utils;

    /// <summary>
    /// A data parser.
    /// </summary>
    internal class Parser
    {
        //<summary>
        //Parses the rfc2822 date to the datetime.
        //</summary>
        //<param name="date">Rfc2822 date in the string form.</param>
        //<returns>The parsed datetime when successfuly parsed; otherwise, year back date.</returns>
        internal static DateTime ParseRfc2822Date(string dateString)
        {
            DateTime resultDatetime = DateTime.Now.AddYears(-1);

            string[] dayArray = dateString.Split(new char[] { ',' }, 2);

            string dayName;

            if (dayArray.Length == 2)
            {
                dayName = dayArray[0];
                dateString = dayArray[1];
            }
            else
            {
                dayName = string.Empty;
            }

            try
            {
                if (dateString.EndsWith(")"))
                {
                    int position = dateString.LastIndexOf("(");
                    dateString = dateString.Substring(0, position);
                }

                string dateTimePart = dateString.Substring(0, dateString.LastIndexOf(" "));
                string timeZone = dateString.Substring(dateString.LastIndexOf(" ") + 1);
                resultDatetime = Convert.ToDateTime(dateTimePart);

                if (!string.IsNullOrEmpty(dayName))
                {
                    if ((resultDatetime.DayOfWeek == DayOfWeek.Friday && dayName != "Fri") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Monday && dayName != "Mon") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Saturday && dayName != "Sat") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Sunday && dayName != "Sun") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Thursday && dayName != "Thu") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Tuesday && dayName != "Tue") ||
                        (resultDatetime.DayOfWeek == DayOfWeek.Wednesday && dayName != "Wed")
                        )
                    {
                        throw new FormatException("Invalid day of the week.");
                    }
                }

                if (Regex.IsMatch(timeZone, "[+\\-][0-9][0-9][0-9][0-9]"))
                {
                    string hours = timeZone.Substring(1, 2);
                    string minutes = timeZone.Substring(3, 2);

                    if (timeZone.StartsWith("+"))
                    {
                        resultDatetime.AddHours(Convert.ToInt32(hours));
                        resultDatetime.AddMinutes(Convert.ToInt32(minutes));
                    }
                    else if (timeZone.StartsWith("-"))
                    {
                        resultDatetime.AddHours(-Convert.ToInt32(hours));
                        resultDatetime.AddMinutes(-Convert.ToInt32(minutes));
                    }
                    else 
                    {
                        throw new FormatException("Invalid time zone.");
                    }
                }
                else
                {
                    switch (timeZone)
                    {
                        case "A":
                            resultDatetime = resultDatetime.AddHours(1);
                            break;
                        case "B":
                            resultDatetime = resultDatetime.AddHours(2);
                            break;
                        case "C":
                            resultDatetime = resultDatetime.AddHours(3);
                            break;
                        case "D":
                            resultDatetime = resultDatetime.AddHours(4);
                            break;
                        case "E":
                            resultDatetime = resultDatetime.AddHours(5);
                            break;
                        case "F":
                            resultDatetime = resultDatetime.AddHours(6);
                            break;
                        case "G":
                            resultDatetime = resultDatetime.AddHours(7);
                            break;
                        case "H":
                            resultDatetime = resultDatetime.AddHours(8);
                            break;
                        case "I":
                            resultDatetime = resultDatetime.AddHours(9);
                            break;
                        case "K":
                            resultDatetime = resultDatetime.AddHours(10);
                            break;
                        case "L":
                            resultDatetime = resultDatetime.AddHours(11);
                            break;
                        case "M":
                            resultDatetime = resultDatetime.AddHours(12);
                            break;
                        case "N":
                            resultDatetime = resultDatetime.AddHours(-1);
                            break;
                        case "O":
                            resultDatetime = resultDatetime.AddHours(-2);
                            break;
                        case "P":
                            resultDatetime = resultDatetime.AddHours(-3);
                            break;
                        case "Q":
                            resultDatetime = resultDatetime.AddHours(-4);
                            break;
                        case "R":
                            resultDatetime = resultDatetime.AddHours(-5);
                            break;
                        case "S":
                            resultDatetime = resultDatetime.AddHours(-6);
                            break;
                        case "T":
                            resultDatetime = resultDatetime.AddHours(-7);
                            break;
                        case "U":
                            resultDatetime = resultDatetime.AddHours(-8);
                            break;
                        case "V":
                            resultDatetime = resultDatetime.AddHours(-9);
                            break;
                        case "W":
                            resultDatetime = resultDatetime.AddHours(-10);
                            break;
                        case "X":
                            resultDatetime = resultDatetime.AddHours(-11);
                            break;
                        case "Y":
                            resultDatetime = resultDatetime.AddHours(-12);
                            break;
                        case "Z":
                        case "UT":
                        case "GMT":
                            break;
                        case "EST":
                            resultDatetime = resultDatetime.AddHours(5);
                            break;
                        case "EDT":
                            resultDatetime = resultDatetime.AddHours(4);
                            break;
                        case "CST":
                            resultDatetime = resultDatetime.AddHours(6);
                            break;
                        case "CDT":
                            resultDatetime = resultDatetime.AddHours(5);
                            break;
                        case "MST":
                            resultDatetime = resultDatetime.AddHours(7);
                            break;
                        case "MDT":
                            resultDatetime = resultDatetime.AddHours(6);
                            break;
                        case "PST":
                            resultDatetime = resultDatetime.AddHours(8);
                            break;
                        case "PDT":
                            resultDatetime = resultDatetime.AddHours(7);
                            break;
                        default:
                            throw new FormatException("Invalid time zone.");
                    }
                }
            }
            catch
            {
                // Invalid date, return Now - 1 year.
            }

            return resultDatetime;
        }

        /// <summary>
        /// Parses the MailAddress from the string.
        /// </summary>
        /// <param name="mailAddressString">The mail address string.</param>
        /// <returns>A parsed mail address.</returns>
        internal static MailAddress ParseMailAddress(string mailAddressString)
        {
            string emailAddress;
            string displayName;
            int position;

            string emailAddressString;

            if (mailAddressString.StartsWith("NIL"))
            {
                displayName = string.Empty;

                emailAddressString = mailAddressString.Substring("NIL NIL ".Length);
            }
            else
            {
                position = mailAddressString.IndexOf(" NIL ");
                string displayNameString = mailAddressString.Substring(0, position).Trim(new char[] { '"' });
                displayName = StringEncoder.IsEncoded(displayNameString) ? StringEncoder.DecodeHeaderValue(displayNameString) : displayNameString;
                emailAddressString = mailAddressString.Substring(displayNameString.Length + 2 + " NIL ".Length);
            }

            position = emailAddressString.GetQuotedString().Length + 3;
            string firstPart = emailAddressString.GetQuotedString();
            string secondPart = emailAddressString.Substring(position).GetQuotedString();

            emailAddress = firstPart + "@" + secondPart;

            return string.IsNullOrEmpty(displayName) ? new MailAddress(emailAddress) : new MailAddress(emailAddress, displayName);
        }
    }
}
